package cn.jsj.onclock.service.impl;

import cn.jsj.onclock.dao.UserDao;
import cn.jsj.onclock.exception.CustomException;
import cn.jsj.onclock.exception.UploadHeadPictureException;
import cn.jsj.onclock.interceptor.TokenCensor;
import cn.jsj.onclock.management.WeChatLoginResult;
import cn.jsj.onclock.management.WeChatUserManagement;
import cn.jsj.onclock.pojo.UserEnt;
import cn.jsj.onclock.pojo.identity.PictureFileType;
import cn.jsj.onclock.pojo.identity.UserIdentityEnum;
import cn.jsj.onclock.service.UserService;
import io.micrometer.core.instrument.util.StringUtils;
import net.coobird.thumbnailator.Thumbnails;
import org.hibernate.validator.constraints.Length;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.*;
import java.util.UUID;
import java.util.concurrent.TimeUnit;


/**
 * @author 卢斌
 */
@Service
public class UserServiceImpl implements UserService {

    private final UserDao userDao;

    private final WeChatUserManagement weChatUserManagement;

    private final TokenCensor tokenCensor;

    private final RedisTemplate<String, Serializable> redisTemplate;

    /**
     * 用户头像文件最大限制，单位字节
     */
    @Value("${user-picture-limit-size}")
    private Long maxPictureSize;


    /**
     * 超级管理员登录的安全码的redis前缀
     */
    @Value("${super-admin-security-code-redis-prefix}")
    private String superAdminPcLoginCode;

    @Value("${super-admin-security-code-limit-time}")
    private Integer superAdminPcCodeLimitSecond;

    /**
     * 用户头像文件保存根路径
     */
    @Value("${user-picture-location}")
    private String userHeadPictureSaveRootLocation;

    /**
     * 图片压缩阈值
     */
    @Value("${user-picture-compression-size}")
    private long compressionSize;


    @Autowired
    public UserServiceImpl(UserDao userDao, WeChatUserManagement weChatUserManagement, TokenCensor tokenCensor, RedisTemplate<String, Serializable> redisTemplate) {
        this.userDao = userDao;
        this.weChatUserManagement = weChatUserManagement;
        this.tokenCensor = tokenCensor;
        this.redisTemplate = redisTemplate;
    }

    /**
     * 用户登录与注册；
     * - 存在用户信息则登录，数据库中如果没有该用户的openId则先完成基础的注册
     *
     * @param code 登录code
     * @return token
     */
    @Override
    public String userLogin(String code) {
        // 调用微信小程序官方登录接口获取OpenId和SessionKey
        WeChatLoginResult loginResult = weChatUserManagement.requestLogin(code);
        // 根据用户的openId尝试获取用户实体信息
        UserEnt userEnt = userDao.getUserEntityByOpenId(loginResult.getOpenId());
        // 如果无法拿到用户信息，说明用户是第一次登录则开始注册过程
        if (userEnt == null) {
            //注册一个新用户，仅包含最基本的信息（openId，默认的用户角色）
            initRegister(loginResult.getOpenId());
            userEnt = userDao.getUserEntityByOpenId(loginResult.getOpenId());
        } else {
            if (Boolean.TRUE.equals(userDao.userIsInBlackList(userEnt.getId()))) {
                throw new CustomException("系统检测到您尝试过使用自动化脚本执行本软件，现已经将您的账号永久封停");
            }
        }
        // 签发token，返回给用户
        return tokenCensor.tokenIssue(userEnt.getId(), userEnt.getUserIdentityEnum(), loginResult.getSessionKey());
    }


    /**
     * 获取用户个人基本信息
     *
     * @param userId 用户Id
     * @return 该用户的个人信息实体
     */
    @Override
    public UserEnt getUserInfo(Long userId) {
        return userDao.getUserEntityById(userId);
    }


    /**
     * 新增一个用户，仅包含openId信息和默认用户角色
     *
     * @param openId    用户openId
     */
    @Override
    public void initRegister(String openId) {
        userDao.insertBaseUser(openId);
    }

    /**
     * 更新用户的个人信息
     *
     * @param userId         用户Id
     * @param name           用户新姓名
     * @param teleNumber     用户手机号
     * @param orgDescription 机构简介
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUserInformation(Long userId, String name, String teleNumber, String orgDescription) {
        boolean allParamIsEmpty = true;
        UserEnt updateBody = new UserEnt();
        updateBody.setId(userId);
        if (StringUtils.isNotBlank(name)) {
            updateBody.setName(name);
            allParamIsEmpty = false;
        }
        if (StringUtils.isNotBlank(teleNumber)) {
            updateBody.setTeleNumber(teleNumber);
            allParamIsEmpty = false;
        }
        if (StringUtils.isNotBlank(orgDescription)) {
            updateBody.setOrgName(orgDescription);
            allParamIsEmpty = false;
        }
        if (allParamIsEmpty) {
            throw new CustomException("找不到可更新的个人信息");
        }
        Integer influenceNumber = userDao.update(updateBody);
        if (influenceNumber == 0) {
            throw new CustomException("您的个人信息未进行任何修改");
        }
    }

    /**
     * 用户更换头像
     *
     * @param userId      用户Id
     * @param pictureFile 用户上传的文件
     */
    @Override
    public void saveUserHeadPicture(Long userId, MultipartFile pictureFile) {
        if (pictureFile.getSize() > maxPictureSize) {
            throw new CustomException("您选择的文件大小超过限制");
        }
        if (pictureFile.getSize() == 0) {
            throw new CustomException("请选择有效图片");
        }

        // 获取用户上传的新头像文件的全名称
        String fileName = pictureFile.getOriginalFilename();
        if (StringUtils.isBlank(fileName)) {
            throw new CustomException("请选择有效图片");
        }
        // 获取用户上传的文件的后缀名
        String[] fileNames = fileName.split("\\.");
        String fileSuffixName = fileNames[fileNames.length - 1];

        // 如果用户上传的文件类型是系统允许的。
        if (PictureFileType.isEffectPictureFileType(fileSuffixName)) {

            // 获取用户个人文件夹位置
            String userSelfDirPath = this.getUserSelfPictureDirPath(userId);
            File userSelfDir = new File(userSelfDirPath);

            // 如果用户第一次上传图片，则为该用户创建一个文件夹
            if (! userSelfDir.exists()) {
                boolean makeDirResult = userSelfDir.mkdirs();
                if (! makeDirResult) {
                    throw new CustomException("文件系统异常，请联系开发人员");
                }
            }

            // 上传头像接口需要进行文件IO操作，容易受到攻击。
            // 针对用户Id，使用Redis上锁，使该用户仅存在一个正在执行的上传头像线程。
            Boolean getLocalResult = redisTemplate.opsForValue().setIfAbsent("headPicture" + userId, userId, 1L, TimeUnit.HOURS);
            if (! Boolean.TRUE.equals(getLocalResult)) {
                throw new CustomException("您上传图片的频率太快了，请稍后");
            }

            // 获取用户新头像保存目标位置
            String relativePath = UUID.randomUUID().toString();
            String aimFilePath = userSelfDirPath + relativePath;
            try {
                // 如果图片文件大小超过压缩阈值，则开始压缩。否则直接保存
                if (pictureFile.getSize() > compressionSize) {
                    double rate = compressionSize * 1.0 / pictureFile.getSize() / 1.32;

                    // 压缩文件大小, 不管图片是什么格式，都输出jpg格式
                    Thumbnails.of(pictureFile.getInputStream()).scale(1f).outputQuality(rate).outputFormat("jpg").toFile(aimFilePath);
                } else {

                    // 文件输出JPG格式
                    Thumbnails.of(pictureFile.getInputStream()).scale(1f).outputQuality(1f).outputFormat("jpg").toFile(aimFilePath);
                }
            } catch (IOException e) {
                throw new UploadHeadPictureException("用户头像文件保存失败！", userId);
            }

            // 解锁
            redisTemplate.delete("headPicture" + userId);

            userDao.updateUserHeadPictureLocation(userId, relativePath + ".jpg");
        } else {
            throw new CustomException("不支持的文件格式");
        }

    }

    /**
     * 获取用户上传的头像
     *
     * @param userId   用户Id
     * @param response httpServletResponse
     */
    @Override
    public void getUserHeadPicture(Long userId, HttpServletResponse response) {

        // 查询该用户当前头像保存的相对路径
        String location = userDao.getUserHeadPictureLocation(userId);
        if (StringUtils.isBlank(location)) {
            throw new CustomException("该用户暂无自定义头像");
        }

        String prefixPath = this.getUserSelfPictureDirPath(userId);

        String headPicturePath = prefixPath + location;
        File headPicture = new File(headPicturePath);

        // 设置文件响应头：最后一个参数是设置下载文件名
        response.setHeader("Content-Disposition", "attachment;filename=" + headPicture.getName());

        // 设置响应头，文件大小
        response.setHeader("Content-Length", String.valueOf(headPicture.length()));

        FileInputStream inputStream = null;
        try {
            inputStream = new FileInputStream(headPicture);
            int b = 0;
            byte[] buffer = new byte[1024];
            while (b != -1) {
                b = inputStream.read(buffer);
                if(b != -1) {
                    response.getOutputStream().write(buffer,0,b);
                }
            }
        } catch (FileNotFoundException e) {
            throw new CustomException("用户头像文件不存在");
        } catch (IOException e) {
            throw new CustomException("文件输出终止");
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                response.getOutputStream().flush();
                System.out.println("文件流已关闭");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 生成一个六位整数，用于超级管理员登录PC端后端，作为超管用户登录的安全码
     *
     * @return 一个六位整数
     */
    @Override
    public Integer createSuperAdminLoginSecurityCode(Long userId, UserIdentityEnum userIdentity) {
        if (! UserIdentityEnum.isAdministrator(userIdentity)) {
            throw new CustomException("本功能仅限超级管理员");
        }
        UserEnt userEnt = userDao.getUserEntityById(userId);
        if (StringUtils.isBlank(userEnt.getTeleNumber())) {
            throw new CustomException("请完善您的手机号信息");
        }
        Integer securityCode;
        // 尝试获取已经存在的安全码
        String redisKey = this.adminTokenRedisKey(userEnt.getTeleNumber());
        Serializable code = redisTemplate.opsForValue().get(redisKey);
        if (code != null) {
            return (int)code;
        }

        // 如果没有可用的安全码，则重新生成一个
        double number = Math.random();
        number = number * Math.pow(10, 8);
        securityCode = (int)number;
        Boolean setResult = redisTemplate.opsForValue().setIfAbsent(redisKey, securityCode, superAdminPcCodeLimitSecond, TimeUnit.SECONDS);
        if (Boolean.TRUE.equals(setResult)) {
            return securityCode;
        }
        throw new CustomException("刷新失败，请重新获取");
    }


    /**
     * 获取用户个人文件夹位置
     *
     * @param userId    用户Id
     * @return  用户个人文件夹位置
     */
    private String getUserSelfPictureDirPath(Long userId) {
        return userHeadPictureSaveRootLocation + userId + "/";
    }

    public String adminTokenRedisKey(String telephone) {
        return superAdminPcLoginCode + telephone;
    }


}
